/*--------------------------------------------- Noble Steed Engine--------------------------------------*
Authors : Daniel Randle, Alex Bourne
Date Created : Mar 5 2013

File:
	nsshader.cpp

Description:
	This file contains the definition for the NSShader class and any associated functions that help
	the ol' girl out
*-----------------------------------------------------------------------------------------------------*/

#include <nsshader.h>
#include <qfile.h>
#include <qtextstream.h>
#include <logfile.h>
#include <nslight.h>
#include <nsmaterial.h>
#include <sstream>

#ifdef DEBUG
#include <debug.h>
#endif

/*---------------------------*
|   class NSShader BEGINS  |
*---------------------------*/

NSShader::NSShader(const std::string & _shaderID, const std::string & fragmentFileName, const std::string & vertexFileName, const std::string & geometryFileName, const std::string & shaderDirectory):shaderID(_shaderID),
	fragFileName(fragmentFileName),
	vertFileName(vertexFileName),
	geomFileName(geometryFileName),
	shaderDir(shaderDirectory),
	objUse(0),
	sProgType(BASE),
	errorState(NONE)
{
	#ifdef DEBUG
	d = NULL;
	#endif
	LogFile("Creating shader with ID : " + shaderID);

	progID = glCreateProgram();  // create program instance
	if (progID == 0)
	{
		LogFile("Error creating shader program");
		errorState = PROGRAM;
		return;
	}

	if ((fragmentFileName != "") || (vertexFileName != ""))
	{
		LogFile("Trying to load shader with ID : " + shaderID);
		if (!loadFromFile(fragmentFileName, vertexFileName, geometryFileName))
			LogFile("Error loading shader with ID : " + shaderID);
	}
}

NSShader::~NSShader()
{
	LogFile("Deleting shader with ID : " + shaderID);
	if (progID != 0)
		glDeleteProgram(progID);
	LogFile("Shader Deleted");
}

std::string NSShader::_getSource(const std::string & fileName)
{
	QFile file(fileName.c_str());  // NEED TO TAKE AWAY QT FUNCTIONS
	std::string shaderText;
	if (!file.open(QIODevice::ReadOnly))
	{
		LogFile("Cannot open shader text file : " + fileName);
		errorState = FILE;
		return "";
	}

	QTextStream in(&file);
	while(!in.atEnd())
		shaderText.append(in.readLine().toStdString()+"\n");

	file.close();
	return shaderText;
}

GLuint NSShader::_loadSource(const std::string & source, ShaderType type)
{
	GLuint shaderID;
	// State what type of shader this will be
	switch (type)
	{
	case (VERTEX):
		shaderID = glCreateShader(GL_VERTEX_SHADER);
		break;
	case (GEOMETRY):
		shaderID = glCreateShader(GL_GEOMETRY_SHADER);
		break;
	case (FRAGMENT):
		shaderID = glCreateShader(GL_FRAGMENT_SHADER);
		break;
	}

	if (shaderID == 0)
	{
		LogFile("Error with shader type");
		errorState = SHADER_TYPE;
		return 0;
	}

	// create arguements for glShaderSource
	const char* c_str = source.c_str();

	glShaderSource(shaderID, 1, &c_str, NULL); // Attach source to shader
	glCompileShader(shaderID);

	GLint worked;
	glGetShaderiv(shaderID, GL_COMPILE_STATUS, &worked);
	if (!worked)
	{
		char infoLog[1024];
		glGetShaderInfoLog(shaderID, sizeof(infoLog), NULL,(GLchar*)infoLog);
		std::string info(infoLog);
		LogFile("Error compiling shader - error info: " + info);
		errorState = COMPILE;
		glDeleteShader(shaderID);
		return 0;
	}
	return shaderID;
}

bool NSShader::_linkShaders(GLuint fragShaderID, GLuint vertShaderID, GLuint geomShaderID)
{
	if (fragShaderID)
		glAttachShader(progID, fragShaderID);
	if (vertShaderID)
		glAttachShader(progID, vertShaderID);
	if (geomShaderID)
		glAttachShader(progID, geomShaderID);

	//_setupTransformFeedback();
	glLinkProgram(progID);

	if (fragShaderID)
		glDeleteShader(fragShaderID);
	if (vertShaderID)
		glDeleteShader(vertShaderID);
	if (geomShaderID)
		glDeleteShader(geomShaderID);

	GLint worked;
	glGetProgramiv(progID, GL_LINK_STATUS, &worked);
	if (worked == 0)
	{
		char infoLog[1024];
		glGetProgramInfoLog(progID, sizeof(infoLog), NULL,(GLchar*)infoLog);
		std::string info(infoLog);
		LogFile("Error linking shader to program - error info: " + info);
		errorState = LINK;
		return false;
	}
	return _validate();
}

bool NSShader::_validate()
{
	glValidateProgram(progID);
	GLint worked;
    glGetProgramiv(progID, GL_VALIDATE_STATUS, &worked);
    if (!worked) 
	{
		char infoLog[1024];
        glGetProgramInfoLog(progID, sizeof(infoLog), NULL, infoLog);
		std::string info(infoLog);
        LogFile("Invalid shader program: " + info);
		errorState = VALIDATE;
        return false;
    }
	return true;
}


bool NSShader::loadFromFile(const std::string & fragmentFileName, const std::string & vertexFileName, const std::string & geometryFileName)
{
	GLuint fragID(0);
	GLuint vertID(0);
	GLuint geomID(0);
	if (fragmentFileName != "")
		fragFileName = fragmentFileName;
	if (vertexFileName != "")
		vertFileName = vertexFileName;
	if (geometryFileName != "")
		geomFileName = geometryFileName;

	if (fragFileName != "")
		fragID = _loadSource(_getSource(shaderDir + "/" + fragFileName));
	if (vertFileName != "")
		vertID = _loadSource(_getSource(shaderDir + "/" + vertFileName), VERTEX);
	if (geomFileName != "")
		geomID = _loadSource(_getSource(shaderDir + "/" + geomFileName), GEOMETRY);

	if ( !_linkShaders(fragID,vertID,geomID) )
	{
		glDeleteProgram(progID);
		progID = 0;
		return false;
	}
	LogFile("Successfully loaded shader");
	return true;
}

void NSShader::_setupTransformFeedback()
{
	if (!outs.empty())
		return;

	const GLchar ** ptr = new const GLchar*[outs.size()];
	for (int i = 0; i < outs.size(); ++i)
	{
		ptr[i] = new const GLchar[outs[i].size()+1];
		ptr[i] = outs[i].c_str();
	}

	glTransformFeedbackVaryings(progID, outs.size(), ptr, GL_INTERLEAVED_ATTRIBS);

	for (int i = 0; i < outs.size(); ++i)
		delete [] ptr[i];
	delete [] ptr;
}

void NSShader::enable() const
{

	glUseProgram(progID);
}

void NSShader::disable() const
{
	glUseProgram(NULL);
}

void NSShader::setAsTransformShader(const std::vector<std::string> & outVars)
{
	outs = outVars;
}

void NSShader::setUniform(const char * variableName, const NSMatrix4Df & data) const
{
	GLuint varIndex;
	NSMatrix4Df mat = data;
	varIndex = glGetUniformLocation( progID, variableName);
	glUniformMatrix4fv(varIndex, 1, GL_TRUE, mat.getDataPtr());
}

void NSShader::setUniform(const char * variableName, const NSMatrix3Df & data) const
{
	GLuint varIndex;
	NSMatrix3Df mat = data;
	varIndex = glGetUniformLocation( progID, variableName);
	glUniformMatrix3fv(varIndex, 1, GL_TRUE, mat.getDataPtr());
}

void NSShader::setUniform(const char * variableName, const NSVec4Df & data) const
{
	GLuint varIndex;
	GLfloat vec[4];
	vec[0] = data.x; vec[1] = data.y; vec[2] = data.z; vec[3] = data.w;
	varIndex = glGetUniformLocation( progID, variableName);
	glUniform4fv(varIndex, 1, vec);
}

void NSShader::setUniform(const char * variableName, const NSVec3Df & data) const
{
	GLuint varIndex;
	GLfloat vec[3];
	vec[0] = data.x; vec[1] = data.y; vec[2] = data.z;
	varIndex = glGetUniformLocation( progID, variableName);
	glUniform3fv(varIndex, 1, vec);
}

void NSShader::setUniform(const char * variableName, const NSVec2Df & data) const
{
	GLuint varIndex;
	GLfloat vec[2];
	vec[0] = data.u; vec[1] = data.v;
	varIndex = glGetUniformLocation( progID, variableName);
	glUniform2fv(varIndex, 1, vec);
}

void NSShader::setUniform(const char * variableName, float data) const
{
	GLuint varIndex;
	varIndex = glGetUniformLocation( progID, variableName);
	glUniform1f(varIndex, data);
}

void NSShader::setUniform(const char * variableName, int data) const
{
	GLuint varIndex;
	varIndex = glGetUniformLocation( progID, variableName);
	glUniform1i(varIndex, data);
}

void NSShader::setUniform(const char * variableName, unsigned int data) const
{
	GLuint varIndex;
	varIndex = glGetUniformLocation( progID, variableName);
	glUniform1ui(varIndex, data);
}

void NSShader::updateUniforms()
{

}

void NSShader::resetErrorState()
{
	errorState = NONE;
}

void NSShader::setShaderID(const std::string & _shaderID)
{
	shaderID = _shaderID;
}

NSShader::Error NSShader::getErrorState() const
{
	return errorState;
}

GLuint NSShader::getProgID() const
{
	return progID;
}

std::string NSShader::getShaderID() const
{
	return shaderID;
}

unsigned int NSShader::getAttributeIndex(const char * name) const
{
	return glGetAttribLocation(progID, name);
}

NSShader::ShaderProgType NSShader::getProgramType() const
{
	return sProgType;
}

unsigned int& NSShader::objectsUsing()
{
	return objUse;
}

#ifdef DEBUG
void NSShader::setDebug(Debug *_d)
{
	d = _d;
}
#endif


/*---------------------------*
|   class NSShader ENDS      |
*---------------------------*/


NSLightShader::NSLightShader(const std::string & _shaderID, 
	const std::string & fragmentFileName, 
	const std::string & vertexFileName, 
	const std::string & geometryFileName, 
	const std::string & shaderDirectory): 
NSShader(_shaderID, fragmentFileName, vertexFileName, geometryFileName, shaderDirectory)
{
	sProgType = LIGHT;
}

void NSLightShader::updateUniforms()
{
	enable();
	setUniform("diffuseMap",NSTexture::Diffuse);
	setUniform("specularMap",NSTexture::Specular);
	setUniform("ambientMap",NSTexture::Ambient);
	setUniform("emissiveMap",NSTexture::Emissive);
	setUniform("normalMap",NSTexture::Height);
	setUniform("normalMap",NSTexture::Normal);
	setUniform("shininessMap",NSTexture::Shininess);
	setUniform("opacityMap",NSTexture::Opacity);
	setUniform("displacementMap",NSTexture::Displacement);
	setUniform("lightMap",NSTexture::Lightmap);
	setUniform("reflectionMap",NSTexture::Reflection);
	setUniform("shadowMap",NSTexture::Shadow);
	disable();
}


NSShadowShader::NSShadowShader(const std::string & _shaderID, 
	const std::string & fragmentFileName, 
	const std::string & vertexFileName,
	const std::string & geometryFileName, 
	const std::string & shaderDirectory): 
NSShader(_shaderID, fragmentFileName, vertexFileName, geometryFileName, shaderDirectory)
{
	sProgType = SHADOW;
	setUniform("shadowMap",NSTexture::Shadow);
	LogFile("Creating ShadowShader and setting uniforms...");
}

NSPickingShader::NSPickingShader(const std::string & _shaderID, 
	const std::string & fragmentFileName, 
	const std::string & vertexFileName,
	const std::string & geometryFileName, 
	const std::string & shaderDirectory): 
NSShader(_shaderID, fragmentFileName, vertexFileName, geometryFileName, shaderDirectory)
{
	sProgType = PICKING;
}